package stdp_rules;

import java.util.List;

import utils.math_utils.RandStream;
import utils.math_utils.RandStream.Rand;

import components.LIFNeuron;
import components.Synapse;

public class LogSTDP extends STDP_Rule {
	
	
	private float alpha;
	private float beta;
	private float cP;
	private float cD;
	private float wCutoff;
	
	
	public LogSTDP(double tConstP, double tConstD, double lRate,
			float noiseVar, float alpha, float beta, float cP, float wCutoff) {
		super(tConstP, tConstD, lRate);
		this.alpha = alpha;
		this.beta = beta;
		this.cP = cP;
		cD = 0.5f * cP;
		this.wCutoff = wCutoff;
		randGen = new RandStream(Rand.GAUSS, 0, Math.sqrt(noiseVar),
				0, 0, false);
	}
	
	public synchronized void applySTDPRule(Synapse s, double tPre, double tPost) {
		double u = tPost - tPre;
		boolean ex = s.getSrc().isExcitatory();
		double st = s.getStrength();
		double deltaW;
		if((u >= 0 && ex) || (u < 0 && !ex)) {
			deltaW = LTPRule(st * Math.exp(-Math.abs(u) / getTConstP()));
		} else {
			deltaW = -LTDRule(st * Math.exp(-Math.abs(u) / getTConstD()));
		}
		deltaW = deltaW * getLRate() * (1 + randGen.next());
		s.setStrength(st + deltaW);
	}
	
	public void applySTDPRule(List<Synapse> syns, double spkTime, boolean in) {
		double spkT2 = 0;
		for (int i = 0, n = syns.size(); i < n; i++) {
			if (in) {
				spkT2 = (syns.get(i).getSrc()).getLastSpike();
				applySTDPRule(syns.get(i), spkT2, spkTime);
			} else {
				spkT2 = (syns.get(i).getTarg()).getLastSpike();
				applySTDPRule(syns.get(i), spkTime, spkT2);
			}
			
		}	
	}

	public void applySTDPRule(Synapse [] syns, double spkTime, boolean in) {
		double spkT2 = 0;
		for (int i = 0, n = syns.length; i < n; i++) {
			if (in) {
				spkT2 = ((LIFNeuron) (syns[i].getSrc())).getLastSpike();
				applySTDPRule(syns[i], spkT2, spkTime);
			} else {
				spkT2 = ((LIFNeuron) (syns[i].getTarg())).getLastSpike();
				applySTDPRule(syns[i], spkTime, spkT2);
			}
			
		}	
	}
	
	public double LTDRule(double strength) {
		if(strength <= wCutoff) {
			return cD * strength / wCutoff;
		} else {
			double h = Math.log(1 + alpha* ((strength / wCutoff) - 1));
			return cD * (1 + (h / alpha));
		}
	}
	
	public double LTPRule(double strength) {
		return cP * Math.exp(-strength / (wCutoff * beta));
	}
	
	
}
